package com.eatme.web.security.authentication.phonecode.provider;
import com.eatme.web.security.authentication.domain.entity.SecurityUser;
import com.eatme.web.security.domain.entity.User;
import com.eatme.web.security.authentication.phonecode.authentication.PhoneCodeAuthenticationToken;
import com.eatme.web.security.authentication.phonecode.service.impl.PhoneCodeSecurityUserServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.util.Assert;

/**
 * @Author: crush
 * @Date: 2021-09-08 21:14
 * version 1.0
 */
public class PhoneCodeAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {

    private static final Logger LOG = LoggerFactory.getLogger(PhoneCodeAuthenticationProvider.class);

    public final PhoneCodeSecurityUserServiceImpl phoneCodeUserDetailsServiceImpl;

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();

    public PhoneCodeAuthenticationProvider(PhoneCodeSecurityUserServiceImpl phoneCodeUserDetailsServiceImpl) {
        this.phoneCodeUserDetailsServiceImpl = phoneCodeUserDetailsServiceImpl;
    }


    /**
     * 认证
     */
    @Override
    public Authentication authenticate(Authentication authentication) {
        if (!supports(authentication.getClass())) {
            LOG.error("authentication cannot support: {}",authentication);
            return null;
        }
        // 首先判断authentication参数必须是一个MobilePhoneAuthenticationToken类型对象
        Assert.isInstanceOf(PhoneCodeAuthenticationToken.class, authentication, this.messages.getMessage("PhoneCodeAuthenticationProvider.onlySupports", "Only PhoneCodeAuthenticationToken is supported"));
//        Assert.isInstanceOf(MobilePhoneAuthenticationToken.class, authentication,
//                ()-> this.messages.getMessage("MobilePhoneAuthenticationProvider.onlySupports", "Only MobilePhoneAuthenticationToken is supported"));
        // 获取authentication参数的principal属性作为手机号
        String phone = authentication.getPrincipal().toString();
        if (StringUtils.isEmpty(phone)) {
            LOG.error("phone cannot be null");
            throw new BadCredentialsException("phone cannot be null");
        }

        // 获取authentication参数的credentials属性作为短信验证码
        String code = authentication.getCredentials().toString();
        if (StringUtils.isEmpty(code)) {
            LOG.error("code cannot be null");
            throw new BadCredentialsException("code cannot be null");
        }
        // 首先判断authentication参数必须是一个MobilePhoneAuthenticationToken类型对象
        LOG.info("PhoneCodeAuthentication authentication request: %s", authentication);
        PhoneCodeAuthenticationToken token = (PhoneCodeAuthenticationToken) authentication;
        // 调用userService服务根据手机号查询用户信息
        SecurityUser user = phoneCodeUserDetailsServiceImpl.findByPhone((String) token.getPrincipal());
        // 校验用户账号是否过期、是否被锁住、是否有效等属性
        userDetailsChecker.check(user);
        // 根据手机号组成的key值去redis缓存中查询发送短信验证码时存储的验证码
        // TODO redis
        String storeCode = "123456";
                //(String) redisTemplate.opsForValue().get("loginVerifyCode:"+phoneNo);
        if (storeCode==null) {
            LOG.error("code is expired");
            throw new BadCredentialsException("code is expired");
        }
        // 用户登录携带的短信验证码与redis中根据手机号查询出来的登录认证短信验证码不一致则抛出验证码错误异常
        if (!code.equals(storeCode)) {
            LOG.error("the code is not correct");
            throw new BadCredentialsException("the code is not correct");
        }
        // 认证成功则返回一个MobilePhoneAuthenticationToken实例对象，principal属性为较为完整的用户信息
        PhoneCodeAuthenticationToken successAuthenticationToken = new PhoneCodeAuthenticationToken(user, code,user.getAuthorities());
        return successAuthenticationToken;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return PhoneCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.messages, "A message source must be set");
        // Assert.notNull(this.redisTemplate, "A RedisTemplate must be set");
        Assert.notNull(this.phoneCodeUserDetailsServiceImpl, "A phoneCodeUserDetailsServiceImpl must be set");
    }

    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }
}